/*-------------------------------------------------------------------------
 *
 * enum.c
 *	  枚举类型的 I/O 函数、操作符、聚合等
 *
 * Copyright (c) 2006-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/enum.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/genam.h"
#include "access/htup_details.h"
#include "access/table.h"
#include "catalog/pg_enum.h"
#include "libpq/pqformat.h"
#include "storage/procarray.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/typcache.h"


static Oid	fc_enum_endpoint(Oid fc_enumtypoid, ScanDirection fc_direction);
static ArrayType *fc_enum_range_internal(Oid fc_enumtypoid, Oid fc_lower, Oid fc_upper);


/*
 * 不允许使用未提交的 pg_enum 元组。
 *
 * 我们需要确保未提交的枚举值不会进入索引。
 * 如果它们进入索引，而我们随后回滚 pg_enum 的添加，
 * 我们将破坏索引，因为没有底层 pg_enum 条目的情况下，
 * 值比较将无法可靠工作。（请注意，删除包含枚举值的堆条目
 * 并不足以确保它不会出现在索引的上层。）
 * 为了做到这一点，我们防止未提交的行用于任何 SQL 级目的。
 * 这比必要的要强，因为该值可能不会插入到表中，
 * 或者它的列上可能没有索引，但这很容易集中执行。
 *
 * 然而，允许使用属于同一事务中创建的枚举类型的未提交值是可以的，
 * 因为那样任何此类索引也将是新的，并且在回滚时将完全消失。
 * 我们目前并没有完全实现这一点，但我们确实允许在 CREATE TYPE AS ENUM
 * 期间自由使用创建的枚举值，这些值当然与枚举类型具有相同的生命周期。
 * （此情况是 "pg_restore -1" 需要的。）
 * 通过 ALTER TYPE ADD VALUE 添加的值目前受到限制，
 * 但如果能证明该枚举类型是在同一事务中早先创建的，
 * 则可以允许使用。（请注意，比较元组 xmins 不适用于此，
 * 因为类型元组可能已经在当前事务中更新。
 * 子事务也会带来需要考虑的危险。）
 *
 * 该函数需要在下面的任何可能返回枚举值的函数中调用（直接或间接）。
 */
static void fc_check_safe_enum_use(HeapTuple fc_enumval_tup)
{
	TransactionId fc_xmin;
	Form_pg_enum fc_en = (Form_pg_enum) GETSTRUCT(fc_enumval_tup);

	
/*
	 * 如果该行被提示为已提交，那么它肯定是安全的。这为所有正常用例提供了快速路径。
	 */
	if (HeapTupleHeaderXminCommitted(fc_enumval_tup->t_data))
		return;

	/*
	 * 通常，当行被读取或加载到 syscache 时，它会被提示为已提交；但以防万一，让我们直接检查 xmin。
	 */
	fc_xmin = HeapTupleHeaderGetXmin(fc_enumval_tup->t_data);
	if (!TransactionIdIsInProgress(fc_xmin) &&
		TransactionIdDidCommit(fc_xmin))
		return;

	/*
	 * 检查枚举值是否未提交。如果没有，它是安全的，因为它是在 CREATE TYPE AS ENUM 期间生成的，并且存活时间不会短于其拥有类型。（对于其他事务生成的值，这也会是错误的；但之前的测试应该已经处理了所有这些情况。）
	 */
	if (!EnumUncommitted(fc_en->oid))
		return;

	/*
	 * 这里可能还有其他测试可以帮助我们缩小不安全条件的范围，但现在就抛出一个异常。
	 */
	ereport(ERROR,
			(errcode(ERRCODE_UNSAFE_NEW_ENUM_VALUE_USAGE),
			 errmsg("unsafe use of new value \"%s\" of enum type %s",
					NameStr(fc_en->enumlabel),
					format_type_be(fc_en->enumtypid)),
			 errhint("New enum values must be committed before they can be used.")));
}


/* 基本 I/O 支持 */

Datum enum_in(PG_FUNCTION_ARGS)
{
	char	   *fc_name = PG_GETARG_CSTRING(0);
	Oid			fc_enumtypoid = PG_GETARG_OID(1);
	Oid			fc_enumoid;
	HeapTuple	fc_tup;

	/* 必须检查长度以防止在 SearchSysCache 内部的 Assert 失败 */
	if (strlen(fc_name) >= NAMEDATALEN)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("invalid input value for enum %s: \"%s\"",
						format_type_be(fc_enumtypoid),
						fc_name)));

	fc_tup = SearchSysCache2(ENUMTYPOIDNAME,
						  ObjectIdGetDatum(fc_enumtypoid),
						  CStringGetDatum(fc_name));
	if (!HeapTupleIsValid(fc_tup))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("invalid input value for enum %s: \"%s\"",
						format_type_be(fc_enumtypoid),
						fc_name)));

	/* 检查在 SQL 中使用它是否安全 */
	fc_check_safe_enum_use(fc_tup);

	/*
	 * 这来自 pg_enum.oid，并在用户表中存储系统 oid。此 oid 必须通过二进制升级得以保留。
	 */
	fc_enumoid = ((Form_pg_enum) GETSTRUCT(fc_tup))->oid;

	ReleaseSysCache(fc_tup);

	PG_RETURN_OID(fc_enumoid);
}

Datum enum_out(PG_FUNCTION_ARGS)
{
	Oid			fc_enumval = PG_GETARG_OID(0);
	char	   *fc_result;
	HeapTuple	fc_tup;
	Form_pg_enum fc_en;

	fc_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(fc_enumval));
	if (!HeapTupleIsValid(fc_tup))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
				 errmsg("invalid internal value for enum: %u",
						fc_enumval)));
	fc_en = (Form_pg_enum) GETSTRUCT(fc_tup);

	fc_result = pstrdup(NameStr(fc_en->enumlabel));

	ReleaseSysCache(fc_tup);

	PG_RETURN_CSTRING(fc_result);
}

/* 二进制 I/O 支持 */
Datum enum_recv(PG_FUNCTION_ARGS)
{
	StringInfo	fc_buf = (StringInfo) PG_GETARG_POINTER(0);
	Oid			fc_enumtypoid = PG_GETARG_OID(1);
	Oid			fc_enumoid;
	HeapTuple	fc_tup;
	char	   *fc_name;
	int			fc_nbytes;

	fc_name = pq_getmsgtext(fc_buf, fc_buf->len - fc_buf->cursor, &fc_nbytes);

	/* 必须检查长度以防止在 SearchSysCache 内部的 Assert 失败 */
	if (strlen(fc_name) >= NAMEDATALEN)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("invalid input value for enum %s: \"%s\"",
						format_type_be(fc_enumtypoid),
						fc_name)));

	fc_tup = SearchSysCache2(ENUMTYPOIDNAME,
						  ObjectIdGetDatum(fc_enumtypoid),
						  CStringGetDatum(fc_name));
	if (!HeapTupleIsValid(fc_tup))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("invalid input value for enum %s: \"%s\"",
						format_type_be(fc_enumtypoid),
						fc_name)));

	/* 检查在 SQL 中使用它是否安全 */
	fc_check_safe_enum_use(fc_tup);

	fc_enumoid = ((Form_pg_enum) GETSTRUCT(fc_tup))->oid;

	ReleaseSysCache(fc_tup);

	pfree(fc_name);

	PG_RETURN_OID(fc_enumoid);
}

Datum enum_send(PG_FUNCTION_ARGS)
{
	Oid			fc_enumval = PG_GETARG_OID(0);
	StringInfoData fc_buf;
	HeapTuple	fc_tup;
	Form_pg_enum fc_en;

	fc_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(fc_enumval));
	if (!HeapTupleIsValid(fc_tup))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
				 errmsg("invalid internal value for enum: %u",
						fc_enumval)));
	fc_en = (Form_pg_enum) GETSTRUCT(fc_tup);

	pq_begintypsend(&fc_buf);
	pq_sendtext(&fc_buf, NameStr(fc_en->enumlabel), strlen(NameStr(fc_en->enumlabel)));

	ReleaseSysCache(fc_tup);

	PG_RETURN_BYTEA_P(pq_endtypsend(&fc_buf));
}

/* 比较函数及相关 */

/*
 * enum_cmp_internal 是所有可见比较函数的共同引擎，除了 enum_eq 和 enum_ne，它们可以直接检查 OID 相等性。
 */
static int fc_enum_cmp_internal(Oid fc_arg1, Oid fc_arg2, FunctionCallInfo fcinfo)
{
	TypeCacheEntry *fc_tcache;

	/*
	 * 在希望不常见的情况下，只有一个或两个 Oid 是奇数时，我们才需要 typcache。这意味着，未能将 flinfo 传递给枚举比较函数的代码的粗略测试可能不会暴露出这个忽视。为了使这种错误更加明显，请断言我们有一个地方可以缓存，即使在快速路径退出时。
	 */
	Assert(fcinfo->flinfo != NULL);

	/* 相等的 OID 无论如何都是相等的 */
	if (fc_arg1 == fc_arg2)
		return 0;

	/* 快速路径：偶数 Oid 已知可以正确比较 */
	if ((fc_arg1 & 1) == 0 && (fc_arg2 & 1) == 0)
	{
		if (fc_arg1 < fc_arg2)
			return -1;
		else
			return 1;
	}

	/* 定位枚举类型的 typcache 条目 */
	fc_tcache = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
	if (fc_tcache == NULL)
	{
		HeapTuple	fc_enum_tup;
		Form_pg_enum fc_en;
		Oid			fc_typeoid;

		/* 获取包含 arg1 的枚举类型的 OID */
		fc_enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(fc_arg1));
		if (!HeapTupleIsValid(fc_enum_tup))
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
					 errmsg("invalid internal value for enum: %u",
							fc_arg1)));
		fc_en = (Form_pg_enum) GETSTRUCT(fc_enum_tup);
		fc_typeoid = fc_en->enumtypid;
		ReleaseSysCache(fc_enum_tup);
		/* 现在定位并记住 typcache 条目 */
		fc_tcache = lookup_type_cache(fc_typeoid, 0);
		fcinfo->flinfo->fn_extra = (void *) fc_tcache;
	}

	/* 剩余的比较逻辑在 typcache.c 中 */
	return compare_values_of_enum(fc_tcache, fc_arg1, fc_arg2);
}

Datum enum_lt(PG_FUNCTION_ARGS)
{
	Oid			fc_a = PG_GETARG_OID(0);
	Oid			fc_b = PG_GETARG_OID(1);

	PG_RETURN_BOOL(fc_enum_cmp_internal(fc_a, fc_b, fcinfo) < 0);
}

Datum enum_le(PG_FUNCTION_ARGS)
{
	Oid			fc_a = PG_GETARG_OID(0);
	Oid			fc_b = PG_GETARG_OID(1);

	PG_RETURN_BOOL(fc_enum_cmp_internal(fc_a, fc_b, fcinfo) <= 0);
}

Datum enum_eq(PG_FUNCTION_ARGS)
{
	Oid			fc_a = PG_GETARG_OID(0);
	Oid			fc_b = PG_GETARG_OID(1);

	PG_RETURN_BOOL(fc_a == fc_b);
}

Datum enum_ne(PG_FUNCTION_ARGS)
{
	Oid			fc_a = PG_GETARG_OID(0);
	Oid			fc_b = PG_GETARG_OID(1);

	PG_RETURN_BOOL(fc_a != fc_b);
}

Datum enum_ge(PG_FUNCTION_ARGS)
{
	Oid			fc_a = PG_GETARG_OID(0);
	Oid			fc_b = PG_GETARG_OID(1);

	PG_RETURN_BOOL(fc_enum_cmp_internal(fc_a, fc_b, fcinfo) >= 0);
}

Datum enum_gt(PG_FUNCTION_ARGS)
{
	Oid			fc_a = PG_GETARG_OID(0);
	Oid			fc_b = PG_GETARG_OID(1);

	PG_RETURN_BOOL(fc_enum_cmp_internal(fc_a, fc_b, fcinfo) > 0);
}

Datum enum_smaller(PG_FUNCTION_ARGS)
{
	Oid			fc_a = PG_GETARG_OID(0);
	Oid			fc_b = PG_GETARG_OID(1);

	PG_RETURN_OID(fc_enum_cmp_internal(fc_a, fc_b, fcinfo) < 0 ? fc_a : fc_b);
}

Datum enum_larger(PG_FUNCTION_ARGS)
{
	Oid			fc_a = PG_GETARG_OID(0);
	Oid			fc_b = PG_GETARG_OID(1);

	PG_RETURN_OID(fc_enum_cmp_internal(fc_a, fc_b, fcinfo) > 0 ? fc_a : fc_b);
}

Datum enum_cmp(PG_FUNCTION_ARGS)
{
	Oid			fc_a = PG_GETARG_OID(0);
	Oid			fc_b = PG_GETARG_OID(1);

	PG_RETURN_INT32(fc_enum_cmp_internal(fc_a, fc_b, fcinfo));
}

/* 枚举编程支持函数 */

/*
 * enum_endpoint：enum_first/enum_last 的共同代码
 */
static Oid fc_enum_endpoint(Oid fc_enumtypoid, ScanDirection fc_direction)
{
	Relation	fc_enum_rel;
	Relation	fc_enum_idx;
	SysScanDesc fc_enum_scan;
	HeapTuple	fc_enum_tuple;
	ScanKeyData fc_skey;
	Oid			fc_minmax;

	/*
	 * 使用 pg_enum_typid_sortorder_index 查找第一个/最后一个枚举成员。
	 * 注意我们不得使用系统缓存。有关更多信息，请查看 catalog/pg_enum.c 中的 RenumberEnumType 注释。
	 */
	ScanKeyInit(&fc_skey,
				Anum_pg_enum_enumtypid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_enumtypoid));

	fc_enum_rel = table_open(EnumRelationId, AccessShareLock);
	fc_enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock);
	fc_enum_scan = systable_beginscan_ordered(fc_enum_rel, fc_enum_idx, NULL,
										   1, &fc_skey);

	fc_enum_tuple = systable_getnext_ordered(fc_enum_scan, fc_direction);
	if (HeapTupleIsValid(fc_enum_tuple))
	{
		/* 检查在 SQL 中使用它是否安全 */
		fc_check_safe_enum_use(fc_enum_tuple);
		fc_minmax = ((Form_pg_enum) GETSTRUCT(fc_enum_tuple))->oid;
	}
	else
	{
		/* 只应在枚举为空时发生 */
		fc_minmax = InvalidOid;
	}

	systable_endscan_ordered(fc_enum_scan);
	index_close(fc_enum_idx, AccessShareLock);
	table_close(fc_enum_rel, AccessShareLock);

	return fc_minmax;
}

Datum enum_first(PG_FUNCTION_ARGS)
{
	Oid			fc_enumtypoid;
	Oid			fc_min;

	/*
	 * 我们依赖于能够从调用表达式树中获取特定的枚举类型。请注意，参数的实际值根本没有被检查；特别是，它可能是 NULL。
	 */
	fc_enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
	if (fc_enumtypoid == InvalidOid)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("could not determine actual enum type")));

	/* 使用索引获取 OID */
	fc_min = fc_enum_endpoint(fc_enumtypoid, ForwardScanDirection);

	if (!OidIsValid(fc_min))
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("enum %s contains no values",
						format_type_be(fc_enumtypoid))));

	PG_RETURN_OID(fc_min);
}

Datum enum_last(PG_FUNCTION_ARGS)
{
	Oid			fc_enumtypoid;
	Oid			fc_max;

	/*
	 * 我们依赖于能够从调用表达式树中获取特定的枚举类型。请注意，参数的实际值根本没有被检查；特别是，它可能是 NULL。
	 */
	fc_enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
	if (fc_enumtypoid == InvalidOid)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("could not determine actual enum type")));

	/* 使用索引获取 OID */
	fc_max = fc_enum_endpoint(fc_enumtypoid, BackwardScanDirection);

	if (!OidIsValid(fc_max))
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("enum %s contains no values",
						format_type_be(fc_enumtypoid))));

	PG_RETURN_OID(fc_max);
}

/* enum_range 的 2 个参数变体 */
Datum enum_range_bounds(PG_FUNCTION_ARGS)
{
	Oid			fc_lower;
	Oid			fc_upper;
	Oid			fc_enumtypoid;

	if (PG_ARGISNULL(0))
		fc_lower = InvalidOid;
	else
		fc_lower = PG_GETARG_OID(0);
	if (PG_ARGISNULL(1))
		fc_upper = InvalidOid;
	else
		fc_upper = PG_GETARG_OID(1);

	/*
	 * 我们依赖于能够从调用表达式树中获取特定的枚举类型。通用类型机制应确保两者属于同一类型。
	 */
	fc_enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
	if (fc_enumtypoid == InvalidOid)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("could not determine actual enum type")));

	PG_RETURN_ARRAYTYPE_P(fc_enum_range_internal(fc_enumtypoid, fc_lower, fc_upper));
}

/* enum_range 的 1 个参数变体 */
Datum enum_range_all(PG_FUNCTION_ARGS)
{
	Oid			fc_enumtypoid;

	/*
	 * 我们依赖于能够从调用表达式树中获取特定的枚举类型。请注意，参数的实际值根本没有被检查；特别是，它可能是 NULL。
	 */
	fc_enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0);
	if (fc_enumtypoid == InvalidOid)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("could not determine actual enum type")));

	PG_RETURN_ARRAYTYPE_P(fc_enum_range_internal(fc_enumtypoid,
											  InvalidOid, InvalidOid));
}

static ArrayType * fc_enum_range_internal(Oid fc_enumtypoid, Oid fc_lower, Oid fc_upper)
{
	ArrayType  *fc_result;
	Relation	fc_enum_rel;
	Relation	fc_enum_idx;
	SysScanDesc fc_enum_scan;
	HeapTuple	fc_enum_tuple;
	ScanKeyData fc_skey;
	Datum	   *fc_elems;
	int			fc_max,
				fc_cnt;
	bool		fc_left_found;

	/*
	 * 使用 pg_enum_typid_sortorder_index 按顺序扫描枚举成员。
	 * 注意我们不得使用系统缓存。有关更多信息，请查看 catalog/pg_enum.c 中的 RenumberEnumType 注释。
	 */
	ScanKeyInit(&fc_skey,
				Anum_pg_enum_enumtypid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_enumtypoid));

	fc_enum_rel = table_open(EnumRelationId, AccessShareLock);
	fc_enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock);
	fc_enum_scan = systable_beginscan_ordered(fc_enum_rel, fc_enum_idx, NULL, 1, &fc_skey);

	fc_max = 64;
	fc_elems = (Datum *) palloc(fc_max * sizeof(Datum));
	fc_cnt = 0;
	fc_left_found = !OidIsValid(fc_lower);

	while (HeapTupleIsValid(fc_enum_tuple = systable_getnext_ordered(fc_enum_scan, ForwardScanDirection)))
	{
		Oid			fc_enum_oid = ((Form_pg_enum) GETSTRUCT(fc_enum_tuple))->oid;

		if (!fc_left_found && fc_lower == fc_enum_oid)
			fc_left_found = true;

		if (fc_left_found)
		{
			/* 检查在 SQL 中使用它是否安全 */
			fc_check_safe_enum_use(fc_enum_tuple);

			if (fc_cnt >= fc_max)
			{
				fc_max *= 2;
				fc_elems = (Datum *) repalloc(fc_elems, fc_max * sizeof(Datum));
			}

			fc_elems[fc_cnt++] = ObjectIdGetDatum(fc_enum_oid);
		}

		if (OidIsValid(fc_upper) && fc_upper == fc_enum_oid)
			break;
	}

	systable_endscan_ordered(fc_enum_scan);
	index_close(fc_enum_idx, AccessShareLock);
	table_close(fc_enum_rel, AccessShareLock);

	/* 并构建结果数组 */
	/* 注意这硬编码了一些有关 Oid 表示的细节 */
	fc_result = construct_array(fc_elems, fc_cnt, fc_enumtypoid,
							 sizeof(Oid), true, TYPALIGN_INT);

	pfree(fc_elems);

	return fc_result;
}
